/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "World.hpp"
#include "WorldManager.hpp"
#include "BundleInfo.hpp"

#include <boost/date_time.hpp>

#include "Faction.hpp"
#include "FileDefs.hpp"
#include "Game.hpp"
#include "gen/netmsg.pb.h"
#include "gen/savemsg.pb.h"
#include "MessageFile.hpp"
#include "Landscape.hpp"
#include "Presentation.hpp"
#include "SimNetLogic.hpp"

namespace spelprog2 {
namespace sim {

const Int SPATIAL_HASH_CELL_SIZE = Int(16) * SCENE_TO_WORLD_SCALER;

World::World(Int seed, const Landscape & landscape, WorldSignals & signals) :
    rng_(seed),
    signals_(signals),
    landscape_(landscape),
    units_(SPATIAL_HASH_CELL_SIZE, SPATIAL_HASH_CELL_SIZE,
        landscape.getSpatials().getExtents().x, landscape.getSpatials().getExtents().y),
    factions_(),
    nextTeamId_(0),
    nextProjectileId_(1)
{
}

World::~World()
{
    for (Factions::iterator it = factions_.begin(), end = factions_.end(); it != end; ++it) {
        delete *it;
    }
}

Projectile & World::addProjectile(const Projectile & projectile)
{
    projectiles_.push_back(projectile);
    projectiles_.back().setId(nextProjectileId_);
    nextProjectileId_ += 1;

    return projectiles_.back();
}

Int World::createTeam(const IntList & members)
{
    assert(!members.empty());

    Int id = nextTeamId_ += 1;

    for (IntList::const_iterator it = members.begin(), end = members.end(); it != end; ++it) {
        factions_[*it]->updateTeam(id);
    }

    return id;
}

WorldManager::WorldManager(Game & game) : net::NetMessageRegistry(),
    game_(game),
    sourceReplay_(),
    recordingReplay_(),
    landscape_(),
    world_(),
    signals_(new WorldSignals),
    updateHandler_(),
    simUpdateSeqNbr_(),
    fullUpdates_(false),
    replayMode_(REPLAYMODE_NONE)
{
}

WorldManager::~WorldManager()
{
}

void WorldManager::registerNetListeners()
{
    using namespace net_logic;
    net::Net & net = game_.getNetworking();

    //create update handler
    updateHandler_ = new HandleFrameUpdate(*this, net); //no problem overwriting this, gets deallocated by smart pointer
    net.addMsgListener<netmsg::FrameUpdate>(WorldNetListener::Pointer(updateHandler_));

    //register all other message types to both this and update handler's message queuer through registerWorldListener()
    registerWorldListener<netmsg::MoveSquad>(WorldNetListener::Pointer(
        new HandleMoveSquad(*this)));
    registerWorldListener<netmsg::ChangeFormation>(WorldNetListener::Pointer(
        new HandleChangeFormation(*this)));
    registerWorldListener<netmsg::StopSquad>(WorldNetListener::Pointer(
        new HandleStopSquad(*this)));
}

void WorldManager::startBackground(Int seed, const std::string & filename)
{
    const std::string landscapeName = getBundlePath(RESOURCE_DEVIATION) + file::MAP_DIRECTORY + filename + "_landscape.txt";

    simUpdateSeqNbr_ = Int(0);
    fullUpdates_ = false;
    replayMode_ = REPLAYMODE_NONE;
    landscape_.reset(new Landscape(landscapeName, game_.getPresentation().getSceneManager()));
    world_.reset(new World(seed, *landscape_.get(), *signals_.get()));
}

void WorldManager::testUnitInit()
{
    Faction & f1 = *new Faction(*world_.get());
    f1.createSquad(Int(30), "goblin", Int2d(Int(400) * SCENE_TO_WORLD_SCALER, Int(700) * SCENE_TO_WORLD_SCALER));
    f1.createSquad(Int(30), "boar", Int2d(Int(400) * SCENE_TO_WORLD_SCALER, Int(800) * SCENE_TO_WORLD_SCALER));
    f1.createSquad(Int(30), "goblin", Int2d(Int(400) * SCENE_TO_WORLD_SCALER, Int(900) * SCENE_TO_WORLD_SCALER));
    f1.createSquad(Int(30), "goblin", Int2d(Int(500) * SCENE_TO_WORLD_SCALER, Int(700) * SCENE_TO_WORLD_SCALER));
    f1.createSquad(Int(30), "boar", Int2d(Int(500) * SCENE_TO_WORLD_SCALER, Int(800) * SCENE_TO_WORLD_SCALER));
    f1.createSquad(Int(30), "goblin", Int2d(Int(500) * SCENE_TO_WORLD_SCALER, Int(900) * SCENE_TO_WORLD_SCALER));

    Faction & f2 = *new Faction(*world_.get());
    f2.createSquad(Int(30), "boar", Int2d(Int(200) * SCENE_TO_WORLD_SCALER, Int(400) * SCENE_TO_WORLD_SCALER));
    f2.createSquad(Int(30), "goblin", Int2d(Int(200) * SCENE_TO_WORLD_SCALER, Int(300) * SCENE_TO_WORLD_SCALER));
    f2.createSquad(Int(30), "goblin", Int2d(Int(200) * SCENE_TO_WORLD_SCALER, Int(200) * SCENE_TO_WORLD_SCALER));
    f2.createSquad(Int(30), "boar", Int2d(Int(300) * SCENE_TO_WORLD_SCALER, Int(400) * SCENE_TO_WORLD_SCALER));
    f2.createSquad(Int(30), "goblin", Int2d(Int(300) * SCENE_TO_WORLD_SCALER, Int(300) * SCENE_TO_WORLD_SCALER));
    f2.createSquad(Int(30), "goblin", Int2d(Int(300) * SCENE_TO_WORLD_SCALER, Int(200) * SCENE_TO_WORLD_SCALER));

    //create two different teams
    World::IntList team;
    team.push_back(0);
    world_->createTeam(team);
    team.clear();
    team.push_back(1);
    world_->createTeam(team);
}

static std::string currentDateAndTime()
{
    std::ostringstream msg;
    const boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
    boost::posix_time::time_facet * const time_facet = new boost::posix_time::time_facet("%Y-%m-%d %H:%M:%S"); //deallocated by stdlib
    msg.imbue(std::locale(msg.getloc(), time_facet));
    msg << now;
    return msg.str();
}

void WorldManager::startBattle(Int seed, const std::string & filename, Int myPlayerId)
{
    savemsg::ReplayFileHeader header;
    header.set_version(file::REPLAY_VERSION);
    header.set_dateandtime(currentDateAndTime());
    header.set_mapfilename(filename);
    header.set_seed(seed);
    header.set_playerid(myPlayerId);

    recordingReplay_.reset(new file::MessageOutputFile(getBundlePath() + file::TEMP_REPLAY_FILENAME, true, file::REPLAY_MAGIC));
    *recordingReplay_ << header;

    startBackground(seed, filename); //for now assume same filename
    testUnitInit();

    fullUpdates_ = true;
    replayMode_ = REPLAYMODE_RECORDING;
    updateHandler_->reset();
    myPlayerId_ = myPlayerId;
}

void WorldManager::startReplay(const std::string & filename)
{
    sourceReplay_.reset(new file::MessageInputFile(filename, file::REPLAY_MAGIC));
    savemsg::ReplayFileHeader header;
    *sourceReplay_ >> header;
    assert(header.version() == file::REPLAY_VERSION); //should've been verified elsewhere

    startBackground((WideInt)header.seed(), header.mapfilename()); //for now assume same filename
    testUnitInit();

    fullUpdates_ = true;
    replayMode_ = REPLAYMODE_PLAYBACK;
    updateHandler_->reset();
    myPlayerId_ = -1;
    updateHandler_->getMessageQueuer()->loadReplayFrames(*sourceReplay_, game_.getNetworking());
}

void WorldManager::simUpdate()
{
    simUpdateSeqNbr_ += 1;

    World::ProjectileRange projectiles = world_->getProjectiles();
    for (World::ProjectileIterator it = projectiles.first, end = projectiles.second; it != end; ) {
        World::ProjectileIterator curr = it++;

        if (!curr->simUpdate(*world_.get(), simUpdateSeqNbr_)) {
            world_->removeProjectile(curr);
        }
    }

    for (Int i = 0, n = world_->getNumFactions(); i != n; i += 1) {
        world_->getFaction(i).simUpdate(simUpdateSeqNbr_);
    }

    world_->getSignals().worldUpdated(*world_.get());
}

file::MessageInputFile & WorldManager::getSourceReplay()
{
    return *sourceReplay_;
}

file::MessageOutputFile & WorldManager::getRecordingReplay()
{
    return *recordingReplay_;
}

void WorldManager::update(float timeDelta)
{
    if (!world_) {
        return;
    }

    if (updateHandler_ && fullUpdates_) {
        updateHandler_->run(timeDelta);
    }
}

void WorldManager::tryMoveSquad(Int squadId, const Int2d & destination)
{
    netmsg::MoveSquad moveSquad;
    moveSquad.mutable_squad()->set_squadid(squadId);
    moveSquad.mutable_destination()->set_x(destination.x);
    moveSquad.mutable_destination()->set_y(destination.y);

    game_.getNetworking().sendToUser(moveSquad, net::SERVER_ID);
}

void WorldManager::tryChangeFormation(Int squadId, auxmsg::Formation formation)
{
    netmsg::ChangeFormation changeFormation;
    changeFormation.mutable_squad()->set_squadid(squadId);
    changeFormation.set_newformation(formation);

    game_.getNetworking().sendToUser(changeFormation, net::SERVER_ID);
}

void WorldManager::tryStopSquad(Int squadId)
{
    netmsg::StopSquad stopSquad;
    stopSquad.mutable_squad()->set_squadid(squadId);

    game_.getNetworking().sendToUser(stopSquad, net::SERVER_ID);
}

}; //namespace sim
}; //namespace spelprog2
